/*
 * Camera1.cpp
 *
 *  Created on: 27/07/2013
 *      Author: gonzalo
 */

#include "Camera.h"

Camera::Camera() :
		up_vector(0.0, 0.0, 1.0) {
	camPitch = 0;
	camYaw = 0;
	movevel = 0.2;
	mousevel = 0.2;
	mi = ismoved = false;
	update_view_matrix();
	perspective_projection(50, 640.0 / 480.0, 1, 1000);
	calculated = false;
}

Camera::Camera(glm::vec3 l) :
		up_vector(0.0, 0.0, 1.0) {
	loc = l;
	camPitch = 0;
	camYaw = 0;
	movevel = 0.2;
	mousevel = 0.2;
	mi = ismoved = false;
	update_view_matrix();
	perspective_projection(50, 640.0 / 480.0, 1, 1000);
	calculated = false;
}

Camera::Camera(glm::vec3 l, float yaw, float pitch) :
		up_vector(0.0, 0.0, 1.0) {
	loc = l;
	camPitch = pitch;
	camYaw = yaw;
	movevel = 0.2;
	mousevel = 0.2;
	mi = ismoved = false;
	update_view_matrix();
	perspective_projection(50, 640.0 / 480.0, 1, 1000);
	calculated = false;
}

Camera::Camera(glm::vec3 l, float yaw, float pitch, float mv, float mov) :
		up_vector(0.0, 0.0, 1.0) {
	loc = l;
	camPitch = pitch;
	camYaw = yaw;
	movevel = mv;
	mousevel = mov;
	mi = false;
	update_view_matrix();
	perspective_projection(50, 640.0 / 480.0, 1, 1000);
	calculated = false;
}

Camera::~Camera() {

}

void Camera::update_view_matrix() {
	float alpha = camYaw * M_PI / 180.0;
	float beta = camPitch * M_PI / 180.0;

	eye_dir.x = cosf(alpha) * cosf(beta);
	eye_dir.y = sinf(alpha) * cosf(beta);
	eye_dir.z = sinf(beta);

	glm::vec3 vector_up = up_vector;

	//std::cout << "eye (" << eye_dir.x << " " << eye_dir.y << " " << eye_dir.z << ")" << std::endl;

	if (camPitch >= 90.0) {
		vector_up = glm::vec3(-cosf(alpha), -sinf(alpha), 1.0);
	} else if (camPitch <= -90.0) {
		vector_up = glm::vec3(cosf(alpha), sinf(alpha), 1.0);
	}

	ViewMatrix = glm::lookAt(loc, loc + eye_dir, vector_up);
}

void Camera::lockCamera() {
	if (camPitch > 90)
		camPitch = 90;
	if (camPitch < -90)
		camPitch = -90;
	if (camYaw < 0.0)
		camYaw += 360.0;
	if (camYaw > 360.0)
		camYaw -= 360;
}

void Camera::rotateCamera(float pitch, float yaw) {
	camYaw += yaw;
	camPitch += pitch;
	lockCamera();
	//std::cout << "(" << camX << "," << camY << "," << camZ << ")->(" << dirX << "," << dirY << "," << dirZ
	//	<< ")" << std::endl;
	//std::cout << camPitch << " " << camYaw << std::endl;

	update_view_matrix();
	calculated = false;
}

void Camera::moveCamera(float dir) {
	float rad = (camYaw + dir) * M_PI / 180.0;
	loc.x += cos(rad) * movevel;
	loc.y += sin(rad) * movevel;

	//std::cout << loc.x << " " << loc.y << " " << loc.z << " " << camYaw << std::endl;
	update_view_matrix();

	calculated = false;
}

void Camera::moveCameraUp(float dir) {
	float yaw = camYaw * M_PI / 180;
	float pitch = camPitch * M_PI / 180;
	float phi = dir * M_PI / 180;
	float deltax = movevel * cosf(pitch) * cosf(yaw + phi);
	float deltay = movevel * cosf(pitch) * sinf(yaw + phi);
	float deltaz = movevel * sinf(pitch) * cosf(phi);

	//std::cout << deltax << " " << deltay << " " << deltaz << std::endl;
	//std::cout << cosf(dir) << std::endl;

	float rad = (camYaw + dir) * M_PI / 180.0;
	loc.x += deltax;
	loc.y += deltay;
	loc.z += deltaz;

	//std::cout << loc.x << " " << loc.y << " " << loc.z << " " << camYaw << " " << camPitch << std::endl;

	update_view_matrix();
	calculated = false;
}

//change the spherical coordinate system to cartesian
glm::vec3 Camera::getVector() {
	//Google->spherical to cartesian
	return (glm::vec3(-cos(camPitch * M_PI / 180.0) * sin(camYaw * M_PI / 180.0),
			sin(camPitch * M_PI / 180.0), -cos(camPitch * M_PI / 180.0) * cos(camYaw * M_PI / 180.0)));
}

glm::vec3 Camera::getLocation() {
	return loc;
}

float Camera::getPitch() {
	return camPitch;
}

float Camera::getYaw() {
	return camYaw;
}
float Camera::getMovevel() {
	return movevel;
}
float Camera::getMousevel() {
	return mousevel;
}

bool Camera::isMouseIn() {
	return mi;
}

void Camera::set_position(glm::vec3 vec) {
	loc = vec;

	update_view_matrix();
	calculated = false;
}

void Camera::lookAt(float pitch, float yaw) {
	camPitch = pitch;
	camYaw = yaw;
	update_view_matrix();
	calculated = false;
}

void Camera::mouseIn(bool b) {
	mi = b;
}

void Camera::perspective_projection(float angle, float aRatio, float near, float far) {
	ProjectionMatrix = glm::perspective(angle, aRatio, near, far);
	calculated = false;
}

void Camera::orthogonal_projection(float left, float right, float bottom, float top, float near, float far) {
	ProjectionMatrix = glm::ortho(left, right, bottom, top, near, far);
	calculated = false;
}

void Camera::setSpeed(float mv, float mov) {
	movevel = mv;
	mousevel = mov;
}

bool Camera::isMoved() {
	return ismoved;
}

void Camera::bind_view_matrix(GLuint program, const char * name) {
	GLuint location = glGetUniformLocation(program, name);
	if (location >= 0) {
		glUniformMatrix4fv(location, 1, GL_FALSE, &ViewMatrix[0][0]);
	}
}

void Camera::bind_projection_matrix(GLuint program, const char * name) {
	GLuint location = glGetUniformLocation(program, name);
	if (location >= 0) {
		glUniformMatrix4fv(location, 1, GL_FALSE, &ProjectionMatrix[0][0]);
	}
}

void Camera::bind_view_projection_matrix(GLuint program, const char * name) {
	GLuint location = glGetUniformLocation(program, name);
	ViewProjectionMatrix = ProjectionMatrix * ViewMatrix;
	if (location >= 0) {
		glUniformMatrix4fv(location, 1, GL_FALSE, &ViewProjectionMatrix[0][0]);
	}
	calculated = true;
}

void Camera::bind_camera_position(GLint program, const char * name) {
	GLuint location = glGetUniformLocation(program, name);
	if (location >= 0) {
		glUniform3fv(location, 1, &loc[0]);
	}
}

glm::mat4 & Camera::getViewMatrix() {
	return this->ViewMatrix;
}

glm::mat4 & Camera::getProjectionMatrix() {
	return this->ProjectionMatrix;
}

const glm::mat4 & Camera::getViewProjectionMatrix() {
	if (!calculated) {
		ViewProjectionMatrix = ProjectionMatrix * ViewMatrix;
		calculated = true;
	}
	return const_cast<const glm::mat4 &>(this->ViewProjectionMatrix);
}
